package Thread;

import org.junit.Test;

import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

/**
 * LockSupport类的park/unpark方法可以更简单灵活地实现synchronized关键字 + Object类的wait/nofity方法所达到的让线程按照指定顺序执行的效果.
 * 或者可以使用Condation await() 和signal()方法
 * LockSupport是Java6引入的一个工具类，用来创建其他同步类和基本线程的阻塞元语，LockSupport类使用一种名为Permit(许可证)来做到阻塞和唤醒功能，
 *           permit只有1 和 0 ，默认是0，阻塞机制底层是Linux内核基于等待队列wait_queue和等待事件wait_event来实现的。它简单灵活，应用广泛。
 *
 *     主要是park()   unpark()两种方法
 *         park()   :  除非许可可用，否则禁用当前线程的调度
 *         unpark() :  如果给定线程不可用，则为其提供许可
 *
 *
 *
 * LockSupport是一个线程工具类，所有的方法都是静态方法,可以让线程在任意位置阻塞，也可以在任意位置唤醒。
 *
 *  1.面试题：
 *     LockSupport 为什么可以先唤醒线程后后阻塞方法？
 *              因为unpark获得了一个凭证，之后再调用park方法，就可以名正言顺的有了凭证去消费，所以不会阻塞。
 *     为什么唤醒两次后，再阻塞两次，但是最终结果还是会阻塞线程？
 *             连续调用两次unpark()后，其凭证数量最多还是为1，其实和调用一次unpark效果一样。而两次park却需要消费两个凭证，证不够，不能放行
 *
 */
public class LockSupport_study {



    final Object obj = new Object();

    public static void main(String[] args) {
        Thread aThread = new Thread(()->{
            try {
                Thread.sleep(3000);
                System.out.println("阻塞之前时间1："+System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("阻塞之前时间："+System.currentTimeMillis());
            LockSupport.park();
            System.out.println("阻塞之后时间："+System.currentTimeMillis());
        },"aaa");
        aThread.start();

        Thread bThread = new Thread(()->{
            LockSupport.unpark(aThread);
        },"bbb");
        bThread.start();
    }

    /**
     * 这是一个简单的等待唤醒机制，run()方法运行，线程被等待，主线程再睡眠1000ms之后再次唤醒，但是这里有个问题：
     *  就是wait()方法和notify()方法会出错，如下错误：
     *    java.lang.IllegalMonitorStateException
     * 	  at java.lang.Object.wait(Native Method)
     *
     * 	 原因：Object的wait()和notify()方法必须要使用同步方法。如下加上同步就没问题了
     *
     */
    @Test
    public void waitAndNotyTest() {

        Thread A = new Thread(()->{
            int sum = 0;
            for (int i = 0; i <=10; i++) {
                sum += i;
            }
            try {
                synchronized (obj){
                    obj.wait();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(sum);
        });
        A.start();
        //睡眠一秒钟，保证线程A已经计算完成，阻塞在wait方法
       try{
           Thread.sleep(1000);
       }catch (InterruptedException ex){

       }
      synchronized (obj){
          obj.notify();
      }
        System.out.println("主线程已经走完了。。。");
    }

    /**
     *  LockSupport类实现上面等待唤醒机制
     *  ,可以看出来，使用更加灵活，更加简单，不用synchronized同步park()和unpark()方法
     *
     *  LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了，实现了线程间的解耦。
     *
     *  unpark函数可以先于park调用，所以不需要担心线程间的执行的先后顺序。
     *
     *  可用对线程进行解耦，无锁块要求，错误的先唤醒后等待，LockSupport也不报错
     *
     *
     *  原理：
     *    底层调用的是UnSafe 的native 方法（元语）
     *    本质是每个线程都有一个permit（凭证），而且只有两个值，一个值0：不阻塞   调用park()方法后，变为1，则该线程阻塞，调用unpark() 又变为0。
     */
    @Test
    public void LockSupportTest() {
        Thread A = new Thread(()->{
                int sum = 0;
                for(int i=0;i<=100;i++){
                    sum+=i;
                }
                LockSupport.park();
                System.out.println(sum);
        });
        A.start();
        //睡眠一秒钟，保证线程A已经计算完成，阻塞在wait方法
        try{
            Thread.sleep(10000);
        }catch (InterruptedException ex){

        }
        LockSupport.unpark(A);
    }





    /**
     * 代码中我们向线程池中扔了一个任务，然后调用Future的get方法，同步阻塞等待线程池的执行结果。
     */
    @Test
    public void test(){
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(1000);
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,15,1000, TimeUnit.SECONDS,queue);

        Future<String> future = poolExecutor.submit(()->{
                TimeUnit.SECONDS.sleep(5);
                return "hello";
        });

       try{
           String result = future.get();//子线程   get方法是如何组塞住当前线程？线程池执行完任务后又是如何唤醒线程的呢？
           System.out.println(result);//主线程  主线程被阻塞等待子线程执行完
       }catch (ExecutionException | InterruptedException e1){

       }

    }
}


